package com.xk.service.impl;

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cms.common.core.exception.ServiceException;
import com.cms.common.core.web.domain.Response;
import com.cms.common.security.utils.SecurityUtils;
import com.xk.config.ExamNacosConfig;
import com.xk.domain.vo.TestPaperingVo;
import com.xk.domain.dto.Topic;
import com.xk.entity.*;
import com.xk.mapper.TestPaperMapper;
import com.xk.service.*;
import com.xk.utils.BeanCopyUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 试卷表(TestPaper)表服务实现类
 *
 * @author YaWu
 * @since 2024-12-14 23:54:32
 */
@Service("testPaperService")
@RequiredArgsConstructor
public class TestPaperServiceImpl extends ServiceImpl<TestPaperMapper, TestPaper> implements TestPaperService {
    /**
     * 考试资格表
     */
    private final ExamEligibilityService examEligibilityService;
    /**
     * 年度类型表
     */
    private final YearTypeService yearTypeService;
    /**
     * 考试配置
     */
    private final ExamNacosConfig  examNacosConfig;
    /**
     * 题库
     */
    private final QuestionBankService questionBankService;
    /**
     * 答题卡
     */
    private final AnswerSheetService answerSheetService;

    /**
     *用户的服务
     */
    private final UserService userService;


    @Override
    @Transactional
    public Response<TestPaperingVo> startTheExamination() {
        // 1.获取用户的考试资格的年度id
        List<Long> userYearTypeId = getUserYearTypeId(); //为null和元素为0则抛出异常
        //2.根据用户当前的年度id,当前时间在合法考试时间
        Long yearId = checkUserYearListIdExamTime(userYearTypeId);
        //3.判断当前年度id是否已经考试过
        if (!checkUserYearListIdExamTime(yearId)) {
            YearType byId = yearTypeService.getById(yearId);
            throw new ServiceException("当前年度:" + byId.getYearTypeName() + "已经考试过,请选择其他年度考试",400);
        }
        //4.根据配置信息,生成试卷,并且返回试卷信息
        return generateTestPaper(yearId);
    }

    /**
     *  判断传入的年度ip是否考试过
     * @param yearId 年度id
     * @return 考试过返回true,未考试过返回false
     */
    private boolean checkUserYearListIdExamTime(Long yearId) {
        LambdaQueryWrapper<TestPaper> queryWrapper = new LambdaQueryWrapper<TestPaper>()
                .eq(TestPaper::getUserId, SecurityUtils.getUserId())
                .eq(TestPaper::getYearTypeId, yearId);
        TestPaper one = this.getOne(queryWrapper);
        if (one!= null) {
            return false;
        }
        return true;
    }



    /**
     * 根据配置信息,生成试卷,并且返回试卷信息
     * @return 试卷信息
     */
    private Response<TestPaperingVo> generateTestPaper(Long yearId) {
         //1.获取从题库中获取单选题,并且写入答题卡的,并且转换答题卡的id
         List<AnswerSheet> singleChoice = saveSingleChoiceReturn(1L);//写入答题卡的,返回答题卡bean
        List<Long> singleChoiceIds =  AnswerSheetToList(singleChoice);

        //2.获取从题库中获取多选题,并且写入答题卡的,并且转换答题卡的id
        List<AnswerSheet> multipleChoice = saveSingleChoiceReturn(2L);//写入答题卡的,返回答题卡bean
        List<Long> multipleChoiceIds = AnswerSheetToList(multipleChoice);

        //3.获取从题库中获取判断题,并且写入答题卡的,并且转换答题卡的id
        List<AnswerSheet> ifQuestion = saveSingleChoiceReturn(3L);//写入答题卡的,返回答题卡bean
        List<Long> ifQuestionIds = AnswerSheetToList(ifQuestion);

        //4.把考试每个题 型出题数量 转为josn格式 [{"1":15},{"2":10},{"3":5}] , 1:单选题出15道,2:多选题出10道,3:判断题出5道
        String typeNumsJson = getMapToJsonString(examNacosConfig.getTypeNums());
        //5.每个题型占总分的百分百比例 转为josn格式 [{"1":20},{"2":30},{"3":50}] , 1:单选题占20%,2:多选题占30%,3:判断题占50%
        String mapToJsonString = getMapToJsonString(examNacosConfig.getConfig());
        //6.生成现在考试开始时间,考试结束时间时间等于+考试时长
        Date startTime = new Date();
        Date endTime = longToEndTime(examNacosConfig.getDuration());
        // 7.把答题卡的id转为josn格式:{ "singleChoice":[1, 2, 3], "multipleChoice":[22, 33], "ifQuestions":[333, 444]}
        String answerSheetIdsJson = getAnswerSheetIdsJson(singleChoiceIds, multipleChoiceIds, ifQuestionIds);
        //8.生成试卷
        User user = userService.getById(SecurityUtils.getUserId());
        TestPaper testPaper = new TestPaper()
                .setYearTypeId(yearId)//年度类型id
                .setUserId(SecurityUtils.getUserId())//用户id
                .setNickName(user.getNickName())//考生名字
                .setUserName(user.getUserName())//学号或者工号
                .setAnswerSheetIds(answerSheetIdsJson)//答题卡id
                .setTypeNums(typeNumsJson)//题型出题数量
                .setSettingTotalPoints(examNacosConfig.getTotalPoints())//总分
                .setPassTotal(examNacosConfig.getTotalPoints())//合格线
                .setConfig(mapToJsonString)//每个题型占总分的百分百比例
                .setBeginTime(startTime)//考试开始时间
                .setEndTime(endTime);//考试结束时间
        boolean save = this.save(testPaper);
        if (!save) {
            throw new ServiceException("生成试卷失败");
        }
        //9.返回试卷信息,根据单选题的
        return Response.success(testPaperResult(testPaper, singleChoice, multipleChoice, ifQuestion));
    }

    /**
     * 根据试卷信息,返回试卷信息
     * @param testPaper 试卷信息
     * @param singleChoice 单选题
     * @param multipleChoice 多选题
     * @param ifQuestion 判断题
     * @return 试卷信息
     */
    private TestPaperingVo testPaperResult(TestPaper testPaper, List<AnswerSheet> singleChoice, List<AnswerSheet> multipleChoice, List<AnswerSheet> ifQuestion) {
        TestPaperingVo dto = new TestPaperingVo();
        //这里只是写入题目,没有写入题目
        List<Topic> singleChoiceList = BeanCopyUtils.copyBeans(singleChoice, Topic.class);
        List<Topic> multipleChoiceList = BeanCopyUtils.copyBeans(multipleChoice, Topic.class);
        List<Topic> ifQuestionList = BeanCopyUtils.copyBeans(ifQuestion, Topic.class);
        //写入选项
        optionSetList(singleChoiceList,singleChoice);
        optionSetList(multipleChoiceList,multipleChoice);
        optionSetList(ifQuestionList,ifQuestion);
        dto.setDuration(examNacosConfig.getDuration())
              .setTestPaperId(testPaper.getTestPaperId())
              .setSettingTotalPoints(testPaper.getSettingTotalPoints())
                .setSingleChoice(singleChoiceList)
                .setMultipleChoice(multipleChoiceList)
                .setIfQuestions(ifQuestionList);
        return dto;
    }

    /**
     * 把答题卡的题目选项转List的字符串,写入到题目集合的选项中 <br>
     * 答题卡选项json格式:[{"A":"选项A"},{"B":"选项B"},{"C":"选项C"},{"D":"选项D"}]
     * @param topicList 题目集合
     * @param answerSheetList 答题卡集合
     */
    private void optionSetList(List<Topic> topicList, List<AnswerSheet> answerSheetList) {
        answerSheetList.stream().forEach(answerSheet -> {
                topicList.stream().forEach(topic -> {
                    if(topic.getTextContext().equals(answerSheet.getTextContext())){
                        List<String> optionsList = JSONUtil.toList(answerSheet.getArrOptions(), String.class);
                        topic.setArrOptions(optionsList);
                        return;
                    }
                });
            });
    }

    /**
     *  转换答题卡集合的id
     * @param list 答题卡集合
     * @return 转换后的id集合
     */
    private List<Long> AnswerSheetToList(List<AnswerSheet> list) {
        return list.stream()
                .map(AnswerSheet::getAnswerSheetId)
                .collect(Collectors.toList());
    }

    /**
     * 获取答题卡的id转为josn格式:{ "singleChoice":[1, 2, 3], "multipleChoice":[22, 33], "ifQuestions":[333, 444]}
     * @param singleChoiceIds 单选题id集合
     * @param multipleChoiceIds 多选题id集合
     * @param ifQuestionIds 判断题id集合
     * @return 答题卡的id转为josn格式
     */
    private String getAnswerSheetIdsJson(List<Long> singleChoiceIds, List<Long> multipleChoiceIds, List<Long> ifQuestionIds) {
        // 创建 JSON 对象
        JSONObject jsonObject = new JSONObject();
        // 将各个集合放入对应的 JSON 字段中
        jsonObject.put("singleChoice", singleChoiceIds);
        jsonObject.put("multipleChoice", multipleChoiceIds);
        jsonObject.put("ifQuestions", ifQuestionIds);
        // 返回 JSON 字符串
        return jsonObject.toString();
    }


    /**
     * 转换考试结束时间
     * @param duration 考试时长(90等于90分钟)
     * @return 考试结束时间
     */
    private Date longToEndTime(Long duration) {
        return new Date(System.currentTimeMillis() + duration * 60 * 1000);
    }

    /**
     * 把map集合转为josn字符串
      * @return json字符串
     */
    private String getMapToJsonString(Map<Long, Long>  map) {
        // 使用流操作将 Map 转为 JSONArray
        JSONArray jsonArray = new JSONArray(
                map.entrySet()
                        .stream()
                        .map(entry -> {
                            JSONObject jsonObject = new JSONObject();
                            jsonObject.put(String.valueOf(entry.getKey()), entry.getValue());
                            return jsonObject;
                        })
                        .collect(Collectors.toList())
        );
        return jsonArray.toString();
    }

    /**
     * 根据传入题目类型,获取题目数量,从题库中随机获取单选题,并且写入答题卡的,并且转换答题卡的id<br>
     * 题目类型:1:单选题,2:多选题,3:判断题
     * @param Type 题目类型
     * @return 答题卡的i
     */
    private List<AnswerSheet> saveSingleChoiceReturn(long Type) {
        Long aLong = examNacosConfig.getTypeNums().get(Type);//获取题目的数量
        //1.从题库中随机获对对应提醒的数量
        List<QuestionBank> list = questionBankService.getRandomQuestions(Type, aLong);
        //2.写入答题卡的
        List<AnswerSheet> beans = BeanCopyUtils.copyBeans(list, AnswerSheet.class);
        Long userId = SecurityUtils.getUserId();
        beans.stream()
                .forEach(e->{
                    e.setUserId(userId);
                });
        beans.stream().forEach(this::resetCommonFields);//重置公共字段
        boolean b = answerSheetService.saveBatch(beans);
        if (!b) {
            throw new ServiceException("写入答题卡失败");
        }
        return beans;
    }


    /**
     * 重置公共字段
     */
    private void resetCommonFields(AnswerSheet AnswerSheet) {
        AnswerSheet.setCreateBy(null)
                .setUpdateTime(null)
                .setCreateBy(null);
    }


    /**
     * 根据用户当前的年度id,当前时间在合法考试时间<br>
     * 不合法则抛出异常
     * @param userYearTypeId 用户当前的年度id集合
     * @return 合法的年度id
     */
    private Long checkUserYearListIdExamTime(List<Long> userYearTypeId) {
        //1.获取当前时间
        Long currentTime = System.currentTimeMillis();
        //2.根据用户的年度id
        List<YearType> typeList = yearTypeService.listByIds(userYearTypeId);
        if(typeList.isEmpty()){
            throw new ServiceException("当前用户没有资格进行考试",400);
        }
        //3.判断当前时间是否在考试时间内
        int count = 0;
        Long yearsId = null;
        for(YearType yearType:typeList){
            if(isExamTime(yearType,currentTime)){
                count++;
                yearsId = yearType.getYearTypeId();
                continue;
            }
        }
        if(count == 0){
            throw new ServiceException("当前时间不在考试时间内",400);
        }
        return yearsId;
    }
    /**
     * 判断当前时间是否在考试时间内
     * @param yearType 年度类型
     * @param currentTime 当前时间
     * @return true:在考试时间内,false:不在考试时间内
     */
    private boolean isExamTime(YearType yearType,Long currentTime){
        // 当前时间转为 Date 类型
        Date currentDate = new Date(currentTime);

        // 获取年度表中的考试开始和结束时间
        Date beginTime = yearType.getBeginTime();
        Date endTime = yearType.getEndTime();

        // 判断当前时间是否在考试时间范围内
        if (currentDate.after(beginTime) && currentDate.before(endTime)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 获取当前用户的考试资格的年度id
     * @return 当前用户的考试资格的年度id集合
     */
    private List<Long> getUserYearTypeId() {
        Long userId = SecurityUtils.getUserId();
        List<ExamEligibility> list = examEligibilityService.list(new LambdaQueryWrapper<ExamEligibility>().eq(ExamEligibility::getUserId, userId));
        if (list.isEmpty()) {
            throw new ServiceException("当前用户没有资格进行考试");
        }
        return list.stream().map(ExamEligibility::getYearTypeId).distinct().distinct().collect(Collectors.toList());
    }
}

